<!DOCTYPE html>
<script src="../resources/js-test.js"></script>
<script>
description('IDL dictionary unittest');

var testObject1 = { foo: 'x' };
var testObject2 = { bar: 'y' };

if (window.internals && internals.dictionaryTest) {
    var dictionaryTest = internals.dictionaryTest();

    debug('Test for setting an empty dictionary');
    dictionaryTest.set({});
    dict = dictionaryTest.get();
    shouldBeUndefined('dict.longMember');
    shouldBe('dict.longMemberWithDefault', '42');
    shouldBeUndefined('dict.longOrNullMember');
    shouldBeNull('dict.longOrNullMemberWithDefault');
    shouldBeUndefined('dict.booleanMember');
    shouldBeUndefined('dict.doubleMember');
    shouldBeUndefined('dict.unrestrictedDoubleMember');
    shouldBeUndefined('dict.stringMember');
    shouldBeEqualToString('dict.stringMemberWithDefault', 'defaultStringValue');
    shouldBeUndefined('dict.byteStringMember');
    shouldBeUndefined('dict.usvStringMember');
    shouldBeUndefined('dict.stringSequenceMember');
    shouldBe('dict.stringSequenceMemberWithDefault', '[]');
    shouldBeUndefined('dict.stringSequenceOrNullMember');
    shouldBeUndefined('dict.elementMember');
    shouldBeUndefined('dict.elementOrNullMember');
    shouldBeUndefined('dict.enumMember');
    shouldBeEqualToString('dict.enumMemberWithDefault', 'foo');
    shouldBeUndefined('dict.enumOrNullMember');
    shouldBeUndefined('dict.objectMember');
    shouldBeNull('dict.objectOrNullMemberWithDefault');
    shouldBeUndefined('dict.doubleOrStringMember');
    shouldBeUndefined('dict.doubleOrStringSequenceMember');
    shouldBeNull('dict.eventTargetOrNullMember');
    shouldBeUndefined('dict.anyMember');
    shouldBeUndefined('dict.callbackFunctionMember');
    debug('');

    debug('Test for setting undefined');
    dictionaryTest.set(undefined);
    dict = dictionaryTest.get();
    shouldBeUndefined('dict.longMember');
    shouldBe('dict.longMemberWithDefault', '42');
    shouldBeUndefined('dict.longOrNullMember');
    shouldBeNull('dict.longOrNullMemberWithDefault');
    shouldBeUndefined('dict.booleanMember');
    shouldBeUndefined('dict.doubleMember');
    shouldBeUndefined('dict.unrestrictedDoubleMember');
    shouldBeUndefined('dict.stringMember');
    shouldBeUndefined('dict.byteStringMember');
    shouldBeUndefined('dict.usvStringMember');
    shouldBeEqualToString('dict.stringMemberWithDefault', 'defaultStringValue');
    shouldBeUndefined('dict.stringSequenceMember');
    shouldBe('dict.stringSequenceMemberWithDefault', '[]');
    shouldBeUndefined('dict.stringSequenceOrNullMember');
    shouldBeUndefined('dict.elementMember');
    shouldBeUndefined('dict.elementOrNullMember');
    shouldBeUndefined('dict.objectMember');
    shouldBeNull('dict.objectOrNullMemberWithDefault');
    shouldBeNull('dict.eventTargetOrNullMember');
    shouldBeUndefined('dict.anyMember');
    shouldBeUndefined('dict.callbackFunctionMember');
    debug('');

    var element1 = document.createElement('div');

    debug('Test for setting valid values');
    dictionaryTest.set({
        longMember: 1,
        longMemberWithDefault: 2,
        longOrNullMember: 3,
        longOrNullMemberWithDefault: 4,
        stringMember: 'modifiedString1',
        stringMemberWithDefault: 'modifiedString2',
        byteStringMember: '\x00\x01\xFE\xFF',
        usvStringMember: '!@#123ABCabc\xA0\uD800\uDC00',
        booleanMember: true,
        doubleMember: 3.14,
        unrestrictedDoubleMember: NaN,
        stringSequenceMember: ['foo', 'bar', 'baz'],
        stringSequenceMemberWithDefault: ['foo', 'bar', 'baz'],
        stringSequenceOrNullMember: [],
        elementMember: element1,
        elementOrNullMember: null,
        enumMember: 'foo',
        enumMemberWithDefault: 'bar',
        enumOrNullMember: 'baz',
        objectMember: testObject1,
        objectOrNullMemberWithDefault: testObject2,
        doubleOrStringMember: 3.14,
        doubleOrStringSequenceMember: [3.14, 'Hello'],
        eventTargetOrNullMember: element1,
        internalEnumOrInternalEnumSequenceMember: 'foo',
        anyMember: 42,
        callbackFunctionMember: (arg1, arg2) => { return 'Hi ' + arg1 + ' and ' + arg2; },
    });
    dict = dictionaryTest.get();
    shouldBe('dict.longMember', '1');
    shouldBe('dict.longMemberWithDefault', '2');
    shouldBe('dict.longOrNullMember', '3');
    shouldBe('dict.longOrNullMemberWithDefault', '4');
    shouldBeEqualToString('dict.stringMember', 'modifiedString1');
    shouldBeEqualToString('dict.stringMemberWithDefault', 'modifiedString2');
    shouldBeEqualToString('dict.byteStringMember', '\x00\x01\xFE\xFF');
    shouldBeEqualToString('dict.usvStringMember', '!@#123ABCabc\xA0\uD800\uDC00');
    shouldBeTrue('dict.booleanMember');
    shouldBe('dict.doubleMember', '3.14');
    shouldBe('dict.unrestrictedDoubleMember', 'NaN');
    shouldBe('dict.stringSequenceMember', '["foo", "bar", "baz"]');
    shouldBe('dict.stringSequenceMemberWithDefault', '["foo", "bar", "baz"]');
    shouldBe('dict.stringSequenceOrNullMember', '[]');
    shouldBe('dict.elementMember', 'element1');
    shouldBeNull('dict.elementOrNullMember');
    shouldBeEqualToString('dict.enumMember', 'foo');
    shouldBeEqualToString('dict.enumMemberWithDefault', 'bar');
    shouldBeEqualToString('dict.enumOrNullMember', 'baz');
    shouldBe('dict.objectMember', 'testObject1');
    shouldBe('dict.objectOrNullMemberWithDefault', 'testObject2');
    shouldBe('dict.doubleOrStringMember', '3.14');
    shouldBe('dict.doubleOrStringSequenceMember', '[3.14, "Hello"]');
    shouldBe('dict.eventTargetOrNullMember', 'element1');
    shouldBeEqualToString('dict.internalEnumOrInternalEnumSequenceMember', 'foo');
    shouldBe('dict.anyMember', '42');
    shouldBeEqualToString('dict.callbackFunctionMember("A", "B")', 'Hi A and B');
    debug('');

    debug('Additional test for union type members');
    dictionaryTest.set({
        doubleOrStringMember: "foo",
    });
    dict = dictionaryTest.get();
    shouldBeEqualToString('dict.doubleOrStringMember', 'foo');
    dictionaryTest.set({
        doubleOrStringMember: {},
    });
    dict = dictionaryTest.get();
    shouldBeEqualToString('dict.doubleOrStringMember', '[object Object]');
    dictionaryTest.set({
        doubleOrStringMember: [],
    });
    dict = dictionaryTest.get();
    shouldBeEqualToString('dict.doubleOrStringMember', '');
    dictionaryTest.set({
        doubleOrStringMember: null,
    });
    dict = dictionaryTest.get();
    shouldBeEqualToString('dict.doubleOrStringMember', 'null');
    dictionaryTest.set({
        doubleOrStringMember: undefined,
    });
    dict = dictionaryTest.get();
    shouldBeUndefined('dict.doubleOrStringMember');
    dictionaryTest.set({
        internalEnumOrInternalEnumSequenceMember: ['foo', 'bar'],
    });
    dict = dictionaryTest.get();
    shouldBe('dict.internalEnumOrInternalEnumSequenceMember', '["foo", "bar"]');

    debug('');

    debug('Test for explicit undefined or null, and missing members');
    dictionaryTest.set({
        enumOrNullMember: null,
        longMember: undefined,
        longMemberWithDefault: null,
        longOrNullMember: undefined,
        longOrNullMemberWithDefault: null,
    });
    dict = dictionaryTest.get();
    shouldBeNull('dict.enumOrNullMember');
    shouldBeUndefined('dict.longMember');
    // ToNumber(null) results in 0.
    shouldBe('dict.longMemberWithDefault', '0');
    // Passing undefined shouldn't invoke any conversion.
    shouldBeUndefined('dict.longOrNullMember');
    // Nullable and its default value is null
    shouldBeNull('dict.longOrNullMemberWithDefault');
    // A non-nullable enum must throw when passed null.
    shouldThrow('dictionaryTest.set({enumMember: null})');
    debug('');

    debug('Test for different values for the any type');
    dictionaryTest.set({
        anyMember: '',
    });
    dict = dictionaryTest.get();
    shouldBeEmptyString('dict.anyMember');
    dictionaryTest.set({
        anyMember: 0,
    });
    dict = dictionaryTest.get();
    shouldBeZero('dict.anyMember');
    dictionaryTest.set({
        anyMember: undefined,
    });
    dict = dictionaryTest.get();
    shouldBeUndefined('dict.anyMember');
    dictionaryTest.set({
        anyMember: false,
    });
    dict = dictionaryTest.get();
    shouldBeFalse('dict.anyMember');
    dictionaryTest.set({
        anyMember: null,
    });
    dict = dictionaryTest.get();
    shouldBeNull('dict.anyMember');
    debug('');

    debug('Test for setting invalid member');
    dictionaryTest.set({invalidMember: 'shouldNotBeSet'});
    dict = dictionaryTest.get();
    shouldBeUndefined('dict.invalidMember');
    debug('');

    debug('Test for setting invalid double value');
    shouldThrow("dictionaryTest.set({doubleMember: NaN})");
    shouldThrow("dictionaryTest.set({doubleMember: Infinity})");
    shouldThrow("dictionaryTest.set({doubleMember: -Infinity})");
    shouldThrow("dictionaryTest.set({doubleMember: 'invalid'})");
    debug('');

    debug('Test for setting invalid ByteString value');
    shouldThrow("dictionaryTest.set({byteStringMember: '\u0100'})");
    debug('');

    debug('Test for setting invalid USVString value');
    dictionaryTest.set({usvStringMember: '_\uDC00_\uD800_\uDC00\uD800_'});
    dict = dictionaryTest.get();
    shouldBeEqualToString('dict.usvStringMember', '_\uFFFD_\uFFFD_\uFFFD\uFFFD_');

    debug('Test for setting invalid enum value');
    shouldThrow("dictionaryTest.set({enumMember: 'invalid'})");
    debug('');

    debug('Test for setting invalid enum value in union sequence');
    shouldThrow("dictionaryTest.set({internalEnumOrInternalEnumSequenceMember: 'invalid'})");
    shouldThrow("dictionaryTest.set({internalEnumOrInternalEnumSequenceMember: ['invalid']})");

    debug('Test for setting invalid object value');
    shouldThrow("dictionaryTest.set({objectMember: 42})");
    shouldThrow("dictionaryTest.set({objectMember: 'invalid'})");
    debug('');

    debug('Test for setting invalid Element value');
    shouldThrow("dictionaryTest.set({elementMember: 42})");
    shouldThrow("dictionaryTest.set({elementMember: testObject1})");
    shouldThrow("dictionaryTest.set({elementMember: []})");
    shouldThrow("dictionaryTest.set({elementMember: document})");
    debug('');

    debug('Test for setting invalid callback function value');
    shouldThrow("dictionaryTest.set({callbackFunctionMember: {}})");
    debug('');

    debug('Test for passing invalid dictionary values');
    shouldThrow("dictionaryTest.set(42)");
    shouldThrow("dictionaryTest.set('string')");
    debug('');

    debug('Test for [Clamp] and [EnforceRange] member');
    dictionaryTest.set({
        longMember: 2147483648,
        longMemberWithClamp: 2147483648
    });
    dict = dictionaryTest.get();
    shouldBe('dict.longMember', '-2147483648');
    shouldBe('dict.longMemberWithClamp', '2147483647');
    shouldThrow('dictionaryTest.set({ longMemberWithEnforceRange: 2147483648 })');
    debug('');

    debug('Test for passing EventTarget');
    dictionaryTest.set({
        eventTargetOrNullMember: window
    });
    dict = dictionaryTest.get();
    shouldBe('dict.eventTargetOrNullMember', 'window');
    dictionaryTest.set({
        eventTargetOrNullMember: null
    });
    dict = dictionaryTest.get();
    shouldBeNull('dict.eventTargetOrNullMember');
    dictionaryTest.set({
        eventTargetOrNullMember: undefined
    });
    dict = dictionaryTest.get();
    shouldBeNull('dict.eventTargetOrNullMember');
    shouldThrow("dictionaryTest.set({eventTargetOrNullMember: 'invalid'})");
    shouldThrow("dictionaryTest.set({eventTargetOrNullMember: 42})");
    shouldThrow("dictionaryTest.set({eventTargetOrNullMember: []})");
    shouldThrow("dictionaryTest.set({eventTargetOrNullMember: {}})");
    debug('');

    debug('Test for derived dictionary');
    dictionaryTest.setDerived({ requiredBooleanMember: true });
    derived = dictionaryTest.getDerived();
    shouldBeUndefined('derived.longMember');
    shouldBe('derived.longMemberWithDefault', '42');
    shouldBeUndefined('derived.longOrNullMember');
    shouldBeNull('derived.longOrNullMemberWithDefault');
    shouldBeUndefined('derived.booleanMember');
    shouldBeUndefined('derived.doubleMember');
    shouldBeUndefined('derived.stringMember');
    shouldBeEqualToString('derived.stringMemberWithDefault', 'defaultStringValue');
    shouldBeUndefined('derived.stringSequenceMember');
    shouldBe('derived.stringSequenceMemberWithDefault', '[]');
    shouldBeUndefined('derived.stringSequenceOrNullMember');
    shouldBeUndefined('derived.elementMember');
    shouldBeUndefined('derived.elementOrNullMember');
    shouldBeUndefined('derived.enumMember');
    shouldBeEqualToString('derived.enumMemberWithDefault', 'foo');
    shouldBeUndefined('derived.enumOrNullMember');
    shouldBeUndefined('derived.objectMember');
    shouldBeNull('derived.objectOrNullMemberWithDefault');
    shouldBeUndefined('derived.derivedStringMember');
    shouldBeEqualToString('derived.derivedStringMemberWithDefault', 'derivedDefaultStringValue');
    shouldBeTrue('derived.requiredBooleanMember');
    debug('');

    dictionaryTest.setDerived({
        longMember: 1,
        stringMemberWithDefault: 'modifiedString',
        derivedStringMember: 'modifiedString2',
        derivedStringMemberWithDefault: 'modifiedString3',
        invalidMember: 'shouldNotBeSet',
        requiredBooleanMember: false,
    });
    derived = dictionaryTest.getDerived();
    shouldBe('derived.longMember', '1');
    shouldBeEqualToString('derived.stringMemberWithDefault', 'modifiedString');
    shouldBeEqualToString('derived.derivedStringMember', 'modifiedString2');
    shouldBeEqualToString('derived.derivedStringMemberWithDefault', 'modifiedString3');
    shouldBeUndefined('derived.invalidMember');
    shouldBeFalse('derived.requiredBooleanMember');
    debug('');

    debug('Test for triple level derived dictionary');
    dictionaryTest.setDerivedDerived({
        longMember: 1,
        derivedStringMember: 'modifiedString',
        derivedDerivedStringMember: 'modifiedString2',
        requiredBooleanMember: false,
    });
    derived = dictionaryTest.getDerivedDerived();
    shouldBe('derived.longMember', '1');
    shouldBeEqualToString('derived.derivedStringMember', 'modifiedString');
    shouldBeEqualToString('derived.derivedDerivedStringMember', 'modifiedString2');
    shouldBeFalse('derived.requiredBooleanMember');
    debug('');

    debug('Test for passing invalid values as derived dictionary');
    shouldThrow("dictionaryTest.setDerived({objectMember: 42, requiredBooleanMember: false })");
    shouldThrow("dictionaryTest.setDerived({})"); // Missing required member.
}
</script>
